/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.force4spring.support;

import org.apache.axis.message.MessageElement;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.force4spring.ForceTemplate;
import org.force4spring.SessionFactoryUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.support.DaoSupport;
import org.springframework.util.StringUtils;

import com.sforce.soap.partner.DescribeSObjectResult;
import com.sforce.soap.partner.fault.ApiFault;
import com.sforce.soap.partner.sobject.SObject;

/**
 * Convenient super class for data access objects used to access Force.com.
 * 
 * <p>
 * Requires a {@link net.sf.hibernate.SessionFactory} to be set, providing a
 * {@link org.springframework.orm.hibernate.HibernateTemplate} based on it to
 * subclasses through the {@link #getHibernateTemplate()} method. Can
 * alternatively be initialized directly with a ForceTemplate, in order to reuse
 * the latter's settings such as the SessionFactory, exception translator, flush
 * mode, etc.
 * 
 * <p>
 * This base class is mainly intended for ForceTemplate usage but can also be
 * used when working with a Force.com Session directly, for example when relying
 * on transactional Sessions. Convenience {@link #getSession} and
 * {@link #releaseSession} methods are provided for that usage style.
 * 
 * <p>
 * This class will create its own ForceTemplate instance if a SessionFactory is
 * passed in. The "allowCreate" flag on that ForceTemplate will be "true" by
 * default. A custom ForceTemplate instance can be used through overriding
 * {@link #createHibernateTemplate}.
 * 
 * @author Juergen Hoeller
 * @author Max Rudman
 * @see #setSessionFactory
 * @see #getHibernateTemplate
 * @see org.springframework.orm.hibernate.HibernateTemplate
 */
public abstract class ForceDaoSupport extends DaoSupport {
	private ForceTemplate forceTemplate;

	/**
	 * Set the Force SessionFactory to be used by this DAO. Will automatically
	 * create a ForceTemplate for the given SessionFactory.
	 * 
	 * @see #createForceTemplate
	 * @see #setForceTemplate
	 */
	public final void setSessionFactory(SessionFactory sessionFactory) {
		this.forceTemplate = createForceTemplate(sessionFactory);
	}

	/**
	 * Create a ForceTemplate for the given SessionFactory. Only invoked if
	 * populating the DAO with a SessionFactory reference!
	 * <p>
	 * Can be overridden in subclasses to provide a ForceTemplate instance with
	 * different configuration, or a custom ForceTemplate subclass.
	 * 
	 * @param sessionFactory
	 *            the Force SessionFactory to create a ForceTemplate for
	 * @return the new ForceTemplate instance
	 * @see #setSessionFactory
	 */
	protected ForceTemplate createForceTemplate(SessionFactory sessionFactory) {
		return new ForceTemplate(sessionFactory);
	}

	/**
	 * Return the Force.com SessionFactory used by this DAO.
	 */
	public final SessionFactory getSessionFactory() {
		return (this.forceTemplate != null ? this.forceTemplate
				.getSessionFactory() : null);
	}

	/**
	 * Set the ForceTemplate for this DAO explicitly, as an alternative to
	 * specifying a SessionFactory.
	 * 
	 * @see #setSessionFactory
	 */
	public final void setForceTemplate(ForceTemplate forceTemplate) {
		this.forceTemplate = forceTemplate;
	}

	/**
	 * Return the ForceTemplate for this DAO, pre-initialized with the
	 * SessionFactory or set explicitly.
	 * <p>
	 * <b>Note: The returned ForceTemplate is a shared instance.</b> You may
	 * introspect its configuration, but not modify the configuration (other
	 * than from within an {@link #initDao} implementation). Consider creating a
	 * custom ForceTemplate instance via
	 * <code>new ForceTemplate(getSessionFactory())</code>, in which case
	 * you're allowed to customize the settings on the resulting instance.
	 */
	public final ForceTemplate getForceTemplate() {
		return this.forceTemplate;
	}

	protected final void checkDaoConfig() {
		if (this.forceTemplate == null) {
			throw new IllegalArgumentException(
					"'sessionFactory' or 'forceTemplate' is required");
		}
	}

	/**
	 * Obtain a Force.com Session, either from the current transaction or a new
	 * one. The latter is only allowed if the
	 * {@link org.springframework.orm.hibernate.HibernateTemplate#setAllowCreate "allowCreate"}
	 * setting of this bean's {@link #setHibernateTemplate HibernateTemplate} is
	 * "true".
	 * <p>
	 * <b>Note that this is not meant to be invoked from HibernateTemplate code
	 * but rather just in plain Hibernate code.</b> Either rely on a
	 * thread-bound Session or use it in combination with
	 * {@link #releaseSession}.
	 * <p>
	 * In general, it is recommended to use HibernateTemplate, either with the
	 * provided convenience operations or with a custom HibernateCallback that
	 * provides you with a Session to work on. HibernateTemplate will care for
	 * all resource management and for proper exception conversion.
	 * 
	 * @return the Hibernate Session
	 * @throws DataAccessResourceFailureException
	 *             if the Session couldn't be created
	 * @throws IllegalStateException
	 *             if no thread-bound Session found and allowCreate=false
	 * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory,
	 *      boolean)
	 */
	protected final ForceSession getSession()
			throws DataAccessResourceFailureException, IllegalStateException {
		return getSession(this.forceTemplate.isAllowCreate());
	}

	/**
	 * Obtain a Hibernate Session, either from the current transaction or a new
	 * one. The latter is only allowed if "allowCreate" is true.
	 * <p>
	 * <b>Note that this is not meant to be invoked from HibernateTemplate code
	 * but rather just in plain Hibernate code.</b> Either rely on a
	 * thread-bound Session or use it in combination with
	 * {@link #releaseSession}.
	 * <p>
	 * In general, it is recommended to use
	 * {@link #getHibernateTemplate() HibernateTemplate}, either with the
	 * provided convenience operations or with a custom
	 * {@link org.springframework.orm.hibernate.HibernateCallback} that provides
	 * you with a Session to work on. HibernateTemplate will care for all
	 * resource management and for proper exception conversion.
	 * 
	 * @param allowCreate
	 *            if a non-transactional Session should be created when no
	 *            transactional Session can be found for the current thread
	 * @return the Hibernate Session
	 * @throws DataAccessResourceFailureException
	 *             if the Session couldn't be created
	 * @throws IllegalStateException
	 *             if no thread-bound Session found and allowCreate=false
	 * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory,
	 *      boolean)
	 */
	protected final ForceSession getSession(boolean allowCreate)
			throws DataAccessResourceFailureException, IllegalStateException {
		return SessionFactoryUtils.getSession(getSessionFactory(), allowCreate);
	}

	/**
	 * Convert the given ApiFault to an appropriate exception from the
	 * <code>org.springframework.dao</code> hierarchy.
	 * <p>
	 * Delegates to the {@link org.force4spring.ForceTemplate#convertApiFault}
	 * method of this DAO's ForceTemplate.
	 * <p>
	 * Typically used in plain Force.com access code, in combination with
	 * {@link #getSession} and {@link #releaseSession}.
	 * 
	 * @param fault
	 *            ApiFault that occured
	 * @return the corresponding DataAccessException instance
	 * @see org.force4spring.ForceTemplate#convertApiFault
	 */
	protected final DataAccessException convertApiFault(ApiFault fault) {
		return this.forceTemplate.convertApiFault(fault);
	}

	/**
	 * Close the given Force.com Session, created via this DAO's SessionFactory,
	 * if it isn't bound to the thread (i.e. isn't a transactional Session).
	 * <p>
	 * Typically used in plain Force.com client code, in combination with
	 * {@link #getSession} and {@link #convertHibernateAccessException}.
	 * 
	 * @param session
	 *            the Session to close
	 * @see org.springframework.orm.hibernate.SessionFactoryUtils#releaseSession
	 */
	protected final void releaseSession(ForceSession session) {
		SessionFactoryUtils.releaseSession(session, getSessionFactory());
	}
}
